package com.cjh.mybatis09.bind;


import com.cjh.mybatis09.mapping.MappedStatement;
import com.cjh.mybatis09.mapping.SqlCommandType;
import com.cjh.mybatis09.session.Configuration;
import com.cjh.mybatis09.session.SqlSession;
import lombok.Getter;

import java.lang.reflect.Method;
import java.util.*;

/**
 * @Descripton 映射器方法
 * @Author cjh
 * @Date 2023/5/26 11:07
 */
public class MapperMethod {
    private final SqlCommand command;
    private final MethodSignature methodSignature;

    public MapperMethod(Class<?> mapperInterface, Method method, Configuration configuration){
        this.command = new SqlCommand(configuration,mapperInterface,method);
        this.methodSignature = new MethodSignature(configuration,method);
    }

    public Object execute(SqlSession sqlSession, Object[] args){
        Object res = null;
        switch (command.getType()){
            case INSERT:
                break;
            case DELETE:
                break;
            case UPDATE:
                break;
            case SELECT:
                Object param = methodSignature.convertArgsToSqlCommandParam(args);
                res  = sqlSession.selectOne(command.getName(),param);
                break;
            default:
                throw new RuntimeException("unknown execution method for:"+command.getName());
        }
        return res;
    }


    /**
     * sql 指令
     */
    @Getter
    public static  class SqlCommand{
        private final String name;
        private final SqlCommandType type;
        public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method){
            String statementName = mapperInterface.getName()+"."+method.getName();
            MappedStatement mappedStatement = configuration.getMappedStatement(statementName);
            name = mappedStatement.getId();
            type = mappedStatement.getSqlCommandType();
        }
    }

    /**
     * 方法签名
     */
    public static class MethodSignature{
        private final SortedMap<Integer,String> params;

        public MethodSignature(Configuration configuration,Method method) {
            this.params = Collections.unmodifiableSortedMap(getParams(method));
        }
        public Object convertArgsToSqlCommandParam(Object[] args){
            final  int paramCount = params.size();
            if (args == null || paramCount == 0) {
                //如果没有参数
                return null;
            }else if (paramCount == 1){
                return args[params.keySet().iterator().next().intValue()];
            }else {
                // 否则，返回一个ParamMap，修改参数名，参数名就是其位置
                final Map<String,Object> param = new ParamMap<>();
                int  i = 0;
                for(Map.Entry<Integer,String> entry:params.entrySet()){
                    // 1.先加一个#{0},#{1},#{2}...参数
                    param.put(entry.getValue(), args[entry.getKey().intValue()]);
                    // issue #71, add param names as param1, param2...but ensure backward compatibility
                    final String genericParamName = "param"+(i+1);
                    if (!param.containsKey(genericParamName)){
                        /*
                         * 2.再加一个#{param1},#{param2}...参数
                         * 你可以传递多个参数给一个映射器方法。如果你这样做了,
                         * 默认情况下它们将会以它们在参数列表中的位置来命名,比如:#{param1},#{param2}等。
                         * 如果你想改变参数的名称(只在多参数情况下) ,那么你可以在参数上使用@Param(“paramName”)注解。
                         */
                        param.put(genericParamName,args[entry.getKey()]);
                    }
                    i++;
                }
                return param;
            }
        }

        private SortedMap<Integer,String> getParams(Method method){
//            用一个treemap 这样就保证还是按参数的先后顺序
            final SortedMap<Integer,String> params = new TreeMap<>();
            final Class<?>[] argTypes = method.getParameterTypes();
            for (int i = 0; i < argTypes.length; i++) {
                String paramName = String.valueOf(params.size());
                // 不做 Param 的实现，这部分不处理。如果扩展学习，需要添加 Param 注解并做扩展实现。
                params.put(i,paramName);
            }
            return params;
        }
    }

    /**
     * 参数map，静态内部类,更严格的get方法，如果没有相应的key，报错
     * @param <V>
     */
    public static class ParamMap<V> extends HashMap<String,V>{
        private static final long serialVersionUID = -2212268410512043556L;

        @Override
        public V get(Object key) {
            if (!super.containsKey(key)){
                throw new RuntimeException("Parameter '" + key + "' not found. Available parameters are " + keySet());
            }
            return super.get(key);
        }
    }
}
